package client;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JavaScriptException;
import com.google.gwt.user.client.ui.*;
import com.google.gwt.user.client.Element;

/**
 * @author Ayzen
 * @version 1.0 29.04.2007 23:44:10
 */
public class Test implements EntryPoint {

  public static final String TAG =
          "<applet name='AppletConnection' ARCHIVE = 'BinaryApplet.jar' CODEBASE = '.' width='500' height='120' " +
          "code='BinaryApplet.class' mayscript><param name='id' value='{0}'></applet>";

  private int id = 0;

  private HTML javaTag;
  private JavaScriptObject applet;

  private String host = "127.0.0.1";
  private int port = 777;
  private boolean connected = false;

  private ScrollPanel scrollPanel;
  private VerticalPanel history = new VerticalPanel();
  private TextBox input = new TextBox();
  private Label lblHost = new Label(" Server: " + host + ':' + port);
  private Label lblAvailBytes = new Label("0");

  public void onModuleLoad() {
    initGUI();
    initApplet();
  }

  private void initApplet() {
    Panel root = RootPanel.get();

    root.remove(javaTag);

    initFunctions(id);

    javaTag = new HTML();
    javaTag.setSize("0", "0");
    javaTag.setHTML(TAG.replaceAll("[{][0][}]", String.valueOf(id)));

    root.add(javaTag);
  }

  private void initGUI() {
    VerticalPanel mainPanel = new VerticalPanel();
    mainPanel.setBorderWidth(1);
    scrollPanel = new ScrollPanel(history);
    scrollPanel.setSize("600", "300");
    input.setWidth("600");

    HorizontalPanel controlPanel = new HorizontalPanel();
    Button btnConnect = new Button("Connect");
    Button btnDisconnect = new Button("Disconnect");
    Button btnChangeServer = new Button("Change server");

    controlPanel.add(btnConnect);
    controlPanel.add(btnDisconnect);
    controlPanel.add(btnChangeServer);
    controlPanel.add(lblHost);
    controlPanel.add(new Label(" Available bytes: "));
    controlPanel.add(lblAvailBytes);

    VerticalPanel buttonsPanel = new VerticalPanel();
    FlowPanel firstRow = new FlowPanel();
    FlowPanel secondRow = new FlowPanel();
    FlowPanel thirdRow = new FlowPanel();
    FlowPanel fourthRow = new FlowPanel();
    Button btnReadBoolean = new Button("Read Boolean");
    Button btnReadByte = new Button("Read Byte");
    Button btnReadBytes = new Button("Read Bytes");
    Button btnReadDouble = new Button("Read Double");
    Button btnReadFloat = new Button("Read Float");
    Button btnReadInt = new Button("Read Int");
    Button btnReadShort = new Button("Read Short");
    Button btnReadUByte = new Button("Read Unsigned Byte");
    Button btnReadUInt = new Button("Read Unsigned Int");
    Button btnReadUShort = new Button("Read Unsigned Short");
    Button btnReadUTF = new Button("Read UTF");
    Button btnReadString = new Button("Read String");
    Button btnWriteBoolean = new Button("Write Boolean");
    Button btnWriteByte = new Button("Write Byte");
    Button btnWriteBytes = new Button("Write Bytes");
    Button btnWriteDouble = new Button("Write Double");
    Button btnWriteFloat = new Button("Write Float");
    Button btnWriteInt = new Button("Write Int");
    Button btnWriteShort = new Button("Write Short");
    Button btnWriteUInt = new Button("Write Unsigned Int");
    Button btnWriteUTF = new Button("Write UTF");
    Button btnWriteString = new Button("Write String");

    firstRow.add(btnReadBoolean);
    firstRow.add(btnReadByte);
    firstRow.add(btnReadBytes);
    firstRow.add(btnReadDouble);
    firstRow.add(btnReadFloat);
    firstRow.add(btnReadInt);
    secondRow.add(btnReadShort);
    secondRow.add(btnReadUByte);
    secondRow.add(btnReadUInt);
    secondRow.add(btnReadUShort);
    thirdRow.add(btnReadUTF);
    thirdRow.add(btnReadString);
    thirdRow.add(btnWriteBoolean);
    thirdRow.add(btnWriteByte);
    thirdRow.add(btnWriteBytes);
    thirdRow.add(btnWriteDouble);
    fourthRow.add(btnWriteFloat);
    fourthRow.add(btnWriteInt);
    fourthRow.add(btnWriteShort);
    fourthRow.add(btnWriteUInt);
    fourthRow.add(btnWriteUTF);
    fourthRow.add(btnWriteString);

    buttonsPanel.add(firstRow);
    buttonsPanel.add(secondRow);
    buttonsPanel.add(thirdRow);
    buttonsPanel.add(fourthRow);

    input.addKeyboardListener(new KeyboardListenerAdapter() {
      public void onKeyPress(Widget sender, char keyCode, int modifiers) {
        if (keyCode == KeyboardListener.KEY_ENTER) ;
//          sendMessage();
      }
    });

    btnConnect.addClickListener(new ClickListener() {
      public void onClick(Widget sender) {
        println(" ** Connecting to server...");
        connectImpl(host, port);
      }
    });

    btnDisconnect.addClickListener(new ClickListener() {
      public void onClick(Widget sender) {
        if (connected) {
          connected = false;
          println(" ** Disconnected from server.");
          disconnectImpl();
        }
        else
          println(" ** Not connected to server.");
      }
    });

    btnChangeServer.addClickListener(new ClickListener() {
      public void onClick(Widget sender) {
        host = inputText("Enter server ip:port", host);
        if (connected) {
          connected = false;
          println(" ** Disconnected from server.");
          disconnectImpl();
        }
        lblHost.setText(" Server: " + host + ':' + port);
        initApplet();
      }
    });

    btnReadBoolean.addClickListener(new ClickListener() {
      public void onClick(Widget sender) {
        if (connected)
          try {
            println(" << " + readBoolean());
          }
          catch (EOFException e) {
            println(" ** EOF is reached.");
          }
          catch (IOException e) {
            println(" ** IO exception while reading...");
          }
          finally {
            try {
              lblAvailBytes.setText(String.valueOf(available()));
            }
            catch (IOException e) {
              e.printStackTrace();
            }
          }
        else
          println(" ** " + "Not connected to server.");
      }
    });

    btnReadByte.addClickListener(new ClickListener() {
      public void onClick(Widget sender) {
        if (connected)
          try {
            println(" << " + readByte());
          }
          catch (EOFException e) {
            println(" ** EOF is reached.");
          }
          catch (IOException e) {
            println(" ** Input/output exception: " + e.getMessage());
          }
          finally {
            try {
              lblAvailBytes.setText(String.valueOf(available()));
            }
            catch (IOException e) {
              e.printStackTrace();
            }
          }
        else
          println(" ** " + "Not connected to server.");
      }
    });

    btnReadBytes.addClickListener(new ClickListener() {
      public void onClick(Widget sender) {
        if (connected) {
          int length = 0;
          try {
            length = Integer.parseInt(inputText("Enter the numbers of bytes to be read: ", String.valueOf(available())));
          }
          catch (IOException e) {
            e.printStackTrace();
          }
          byte[] bytes = new byte[length];

          try {
            readBytes(bytes);
          }
          catch (EOFException e) {
            println(" ** EOF is reached.");
          }
          catch (IOException e) {
            println(" ** IO exception while reading...");
          }

          String bytesStr = "";
          for (int i = 0; i < length; i++)
            bytesStr += bytes[i] + " ";
          println(" << " + bytesStr);

          try {
            lblAvailBytes.setText(String.valueOf(available()));
          }
          catch (IOException e) {
            e.printStackTrace();
          }
        }
        else
          println(" ** " + "Not connected to server.");
      }
    });

    btnReadDouble.addClickListener(new ClickListener() {
      public void onClick(Widget sender) {
        if (connected)
          try {
            println(" << " + readDouble());
          }
          catch (EOFException e) {
            println(" ** EOF is reached.");
          }
          catch (IOException e) {
            println(" ** IO exception while reading...");
          }
          finally {
            try {
              lblAvailBytes.setText(String.valueOf(available()));
            }
            catch (IOException e) {
              e.printStackTrace();
            }
          }
        else
          println(" ** " + "Not connected to server.");
      }
    });

    btnReadFloat.addClickListener(new ClickListener() {
      public void onClick(Widget sender) {
        if (connected)
          try {
            println(" << " + readFloat());
          }
          catch (EOFException e) {
            println(" ** EOF is reached.");
          }
          catch (IOException e) {
            println(" ** IO exception while reading...");
          }
          finally {
            try {
              lblAvailBytes.setText(String.valueOf(available()));
            }
            catch (IOException e) {
              e.printStackTrace();
            }
          }
        else
          println(" ** " + "Not connected to server.");
      }
    });

    btnReadInt.addClickListener(new ClickListener() {
      public void onClick(Widget sender) {
        if (connected)
          try {
            println(" << " + readInt());
          }
          catch (EOFException e) {
            println(" ** EOF is reached.");
          }
          catch (IOException e) {
            println(" ** IO exception while reading...");
          }
          finally {
            try {
              lblAvailBytes.setText(String.valueOf(available()));
            }
            catch (IOException e) {
              e.printStackTrace();
            }
          }
        else
          println(" ** " + "Not connected to server.");
      }
    });

    btnReadShort.addClickListener(new ClickListener() {
      public void onClick(Widget sender) {
        if (connected)
          try {
            println(" << " + readShort());
          }
          catch (EOFException e) {
            println(" ** EOF is reached.");
          }
          catch (IOException e) {
            println(" ** IO exception while reading...");
          }
          finally {
            try {
              lblAvailBytes.setText(String.valueOf(available()));
            }
            catch (IOException e) {
              e.printStackTrace();
            }
          }
        else
          println(" ** " + "Not connected to server.");
      }
    });

    btnReadUByte.addClickListener(new ClickListener() {
      public void onClick(Widget sender) {
        if (connected)
          try {
            println(" << " + readUnsignedByte());
          }
          catch (EOFException e) {
            println(" ** EOF is reached.");
          }
          catch (IOException e) {
            println(" ** IO exception while reading...");
          }
          finally {
            try {
              lblAvailBytes.setText(String.valueOf(available()));
            }
            catch (IOException e) {
              e.printStackTrace();
            }
          }
        else
          println(" ** " + "Not connected to server.");
      }
    });

    btnReadUInt.addClickListener(new ClickListener() {
      public void onClick(Widget sender) {
        if (connected)
          try {
            println(" << " + readUnsignedInt());
          }
          catch (EOFException e) {
            println(" ** EOF is reached.");
          }
          catch (IOException e) {
            println(" ** IO exception while reading...");
          }
          finally {
            try {
              lblAvailBytes.setText(String.valueOf(available()));
            }
            catch (IOException e) {
              e.printStackTrace();
            }
          }
        else
          println(" ** " + "Not connected to server.");
      }
    });

    btnReadUShort.addClickListener(new ClickListener() {
      public void onClick(Widget sender) {
        if (connected)
          try {
            println(" << " + readUnsignedShort());
          }
          catch (EOFException e) {
            println(" ** EOF is reached.");
          }
          catch (IOException e) {
            println(" ** IO exception while reading...");
          }
          finally {
            try {
              lblAvailBytes.setText(String.valueOf(available()));
            }
            catch (IOException e) {
              e.printStackTrace();
            }
          }
        else
          println(" ** " + "Not connected to server.");
      }
    });

    btnReadUTF.addClickListener(new ClickListener() {
      public void onClick(Widget sender) {
        if (connected)
          try {
            println(" << " + readUTF());
          }
          catch (EOFException e) {
            println(" ** EOF is reached.");
          }
          catch (IOException e) {
            println(" ** IO exception while reading...");
          }
          finally {
            try {
              lblAvailBytes.setText(String.valueOf(available()));
            }
            catch (IOException e) {
              e.printStackTrace();
            }
          }
        else
          println(" ** " + "Not connected to server.");
      }
    });

    btnReadString.addClickListener(new ClickListener() {
      public void onClick(Widget sender) {
        if (connected)
          try {
            String encoding = inputText("Enter the name of char set: ", "utf-8");
            int length = Integer.parseInt(inputText("Enter the number of bytes to be read: ", String.valueOf(available())));
            println(" << " + readString(length, encoding));
          }
          catch (EOFException e) {
            println(" ** EOF is reached.");
          }
          catch (IOException e) {
            e.printStackTrace();
          }
          finally {
            try {
              lblAvailBytes.setText(String.valueOf(available()));
            }
            catch (IOException e) {
              e.printStackTrace();
            }
          }
        else
          println(" ** " + "Not connected to server.");
      }
    });

    btnWriteBoolean.addClickListener(new ClickListener() {
      public void onClick(Widget sender) {
        String msg = input.getText();
        println(" >> " + msg);
        input.setText("");

        try {
          writeBoolean(Boolean.valueOf(msg).booleanValue());
          flush();
        }
        catch (IOException e) {
          println(" ** IO Exception: " + e.getMessage());
        }
      }
    });

    btnWriteByte.addClickListener(new ClickListener() {
      public void onClick(Widget sender) {
        String msg = input.getText();
        println(" >> " + msg);
        input.setText("");

        try {
          writeByte(Byte.parseByte(msg));
          flush();
        }
        catch (IOException e) {
          println(" ** IO Exception: " + e.getMessage());
        }
      }
    });

    btnWriteBytes.addClickListener(new ClickListener() {
      public void onClick(Widget sender) {
        String msg = input.getText();
        println(" >> " + msg);
        String[] bytesStr = msg.split(" ");
        input.setText("");

        byte[] bytes = new byte[bytesStr.length];
        for (int i = 0; i < bytesStr.length; i++)
          bytes[i] = Byte.parseByte(bytesStr[i]);

        try {
          writeBytes(bytes);
          flush();
        }
        catch (IOException e) {
          println(" ** IO Exception: " + e.getMessage());
        }
      }
    });

    btnWriteDouble.addClickListener(new ClickListener() {
      public void onClick(Widget sender) {
        String msg = input.getText();
        println(" >> " + msg);
        input.setText("");

        try {
          writeDouble(Double.parseDouble(msg));
          flush();
        }
        catch (IOException e) {
          println(" ** IO Exception: " + e.getMessage());
        }
      }
    });

    btnWriteFloat.addClickListener(new ClickListener() {
      public void onClick(Widget sender) {
        String msg = input.getText();
        println(" >> " + msg);
        input.setText("");

        try {
          flush();
          writeFloat(Float.parseFloat(msg));
        }
        catch (IOException e) {
          println(" ** IO Exception: " + e.getMessage());
        }
      }
    });

    btnWriteInt.addClickListener(new ClickListener() {
      public void onClick(Widget sender) {
        String msg = input.getText();
        println(" >> " + msg);
        input.setText("");

        try {
          writeInt(Integer.parseInt(msg));
          flush();
        }
        catch (IOException e) {
          println(" ** IO Exception: " + e.getMessage());
        }
      }
    });

    btnWriteShort.addClickListener(new ClickListener() {
      public void onClick(Widget sender) {
        String msg = input.getText();
        println(" >> " + msg);
        input.setText("");

        try {
          writeShort(Short.parseShort(msg));
          flush();
        }
        catch (IOException e) {
          println(" ** IO Exception: " + e.getMessage());
        }
      }
    });

    btnWriteUInt.addClickListener(new ClickListener() {
      public void onClick(Widget sender) {
        String msg = input.getText();
        println(" >> " + msg);
        input.setText("");

        try {
          writeUnsignedInt(Long.parseLong(msg));
          flush();
        }
        catch (IOException e) {
          println(" ** IO Exception: " + e.getMessage());
        }
      }
    });

    btnWriteUTF.addClickListener(new ClickListener() {
      public void onClick(Widget sender) {
        String msg = input.getText();
        println(" >> " + msg);
        input.setText("");

        try {
          writeUTF(msg);
          flush();
        }
        catch (IOException e) {
          println(" ** IO Exception: " + e.getMessage());
        }
      }
    });

    btnWriteString.addClickListener(new ClickListener() {
      public void onClick(Widget sender) {
        String msg = input.getText();
        String charSet = inputText("Enter the name of char set: ", "utf-8");

        println(" >> " + msg);
        input.setText("");

        try {
          writeString(msg, charSet);
          flush();
        }
        catch (IOException e) {
          println(" ** IO Exception: " + e.getMessage());
        }
      }
    });

    mainPanel.add(scrollPanel);
    mainPanel.add(input);
    mainPanel.add(controlPanel);
    mainPanel.add(buttonsPanel);

    RootPanel.get().add(mainPanel);

    println("Welcome to Sockets test application :)");
  }

  // Util functions

  private void println(String message) {
    history.add(new Label(message));
    scrollToBottom();
  }

  public void scrollToBottom() {
    scrollToBottomImpl(scrollPanel.getElement());
  }

  private native void scrollToBottomImpl(Element element) /*-{
    element.scrollTop = element.scrollHeight;
  }-*/;

  private native String inputText(String message, String defText)/*-{
    var text = prompt(message, defText);
    if (text != null && text != "")
      return text;
    else
      return defText;
  }-*/;

  // Event dispatchers

  private void onConnect() {
    println(" ** Connected to server.");
    connected = true;
    try {
      writeByte(1);
      flush();
    }
    catch (IOException e) {
      println(" ** IO Exception: " + e.getMessage());
    }
  }

  private void onClose() {
    println(" ** Server closed connection.");
    connected = false;
  }

  private void onIOError(String message) {
    println(" ** Input/output error: " + message + ".");
  }

  private void onSecurityError(String message) {
    println(" ** Security error: " + message + ".");
  }

  private void onData(int loadedBytes) {
    if (connected) {
      int availBytes = 0;
      try {
        availBytes = available();
      }
      catch (IOException e) {
        e.printStackTrace();
      }
      lblAvailBytes.setText(String.valueOf(availBytes));
      println(" << Received " + loadedBytes + " byte(s). Total bytes: " + availBytes);
    }
  }

  // Functions for Applet support

  private native void initFunctions(int id)/*-{
    var instance = this;

    $wnd["javabin_" + id + "_onAppletLoad"] = function(applet) {
      instance.@client.Test::applet = applet;
    }
    $wnd["javabin_" + id + "_onConnect"] = function(success) {
      instance.@client.Test::onConnect()();
    }
    $wnd["javabin_" + id + "_onClose"] = function() {
      instance.@client.Test::onClose()();
    }
    $wnd["javabin_" + id + "_onData"] = function(data) {
      instance.@client.Test::onData(I)(data);
    }
    $wnd["javabin_" + id + "_onSecurityError"] = function(msg) {
      instance.@client.Test::onSecurityError(Ljava/lang/String;)(msg);
    }
    $wnd["javabin_" + id + "_onIOError"] = function(msg) {
      instance.@client.Test::onIOError(Ljava/lang/String;)(msg);
    }
  }-*/;

  private native void connectImpl(String ip, int port)/*-{
    this.@client.Test::applet.connect(ip, port);
  }-*/;

  private native void disconnectImpl()/*-{
    this.@client.Test::applet.close();
  }-*/;

  private static native Object checkResult(Object result)/*-{
    try {
      // Test if result is a string with exception
      var exCharCode = result.charAt(0);
      // readBytes, readUTF and readString methods return strings, if the first character is 'E' it is an exception
      if (exCharCode != 69)
        return result.substring(1);
    }
    catch (e) {
      // It's not a string with exception, so we can return the value
      return result;
    }
    // Here we know that the result is an exception, creating a JS exception
    var ex = new Object();
    ex.name = result.substring(1, 2);
    ex.message = result.substring(2);
    if (ex.message == "null")
      ex.message = null;
    throw ex;
  }-*/;

  private IOException getException(JavaScriptException e) {
    // e.getName contains not correct string (actually it's native string from JS), so e.getName().charAt(0) will
    // always throw an exception. "" + e.getName() is a workaround for this issue.
    String name = "" + e.getName();
    switch (name.charAt(0)) {
      case '1':
        return new IOException(e.getDescription());
      case '2':
        return new EOFException(e.getDescription());
      default:
        return new IOException("Unknown exception");
    }
  }

  private int available() throws IOException {
    try {
      return availableImpl();
    }
    catch (JavaScriptException e) {
      throw getException(e);
    }
  }

  private native int availableImpl()/*-{
    return @client.Test::checkResult(Ljava/lang/Object;)(this.@client.Test::applet.available());
  }-*/;

  private void flush() throws IOException {
    try {
      flushImpl();
    }
    catch (JavaScriptException e) {
      throw getException(e);
    }
  }

  private native void flushImpl()/*-{
    @client.Test::checkResult(Ljava/lang/Object;)(this.@client.Test::applet.flush());
  }-*/;

  private int skip(int bytes) throws IOException {
    try {
      return skipImpl(bytes);
    }
    catch (JavaScriptException e) {
      throw getException(e);
    }
  }

  private native int skipImpl(int bytes)/*-{
    return @client.Test::checkResult(Ljava/lang/Object;)(this.@client.Test::applet.skip(bytes));
  }-*/;

  private boolean readBoolean() throws IOException {
    try {
      return readBooleanImpl();
    }
    catch (JavaScriptException e) {
      throw getException(e);
    }  
  }

  private native boolean readBooleanImpl()/*-{
    return @client.Test::checkResult(Ljava/lang/Object;)(this.@client.Test::applet.readBoolean());
  }-*/;

  private byte readByte() throws IOException {
    try {
      return readByteImpl();
    }
    catch (JavaScriptException e) {
      throw getException(e);
    }
  }

  private native byte readByteImpl()/*-{
    return @client.Test::checkResult(Ljava/lang/Object;)(this.@client.Test::applet.readByte());
  }-*/;

  private int readBytes(byte[] bytes) throws IOException {
    return readBytes(bytes, 0, bytes.length);
  }

  private int readBytes(byte[] bytes, int offset, int length) throws IOException {
    if (bytes == null)
      throw new NullPointerException();

    if (offset < 0 || length < 0 || offset + length > bytes.length)
      throw new IndexOutOfBoundsException();

    if (length == 0)
      return 0;

    int availBytes = available();
    if (availBytes == 0)
      return -1;

    // If available bytes < than a length, it will read all available bytes
    if (availBytes < length)
      length = availBytes;

    String result;
    try {
      result = "" + readBytesImpl(length);
    }
    catch (JavaScriptException e) {
      throw getException(e);
    }

    for (int i = 0; i < length; i++)
      bytes[offset + i] = (byte) result.charAt(i);

    return length;
  }

  private native String readBytesImpl(int length)/*-{
    return @client.Test::checkResult(Ljava/lang/Object;)(this.@client.Test::applet.readBytes(length));
  }-*/;

  private double readDouble() throws IOException {
    try {
      return readDoubleImpl();
    }
    catch (JavaScriptException e) {
      throw getException(e);
    }
  }

  private native double readDoubleImpl()/*-{
    return @client.Test::checkResult(Ljava/lang/Object;)(this.@client.Test::applet.readDouble());
  }-*/;

  private float readFloat() throws IOException {
    try {
      return readFloatImpl();
    }
    catch (JavaScriptException e) {
      throw getException(e);
    }
  }

  private native float readFloatImpl()/*-{
    return @client.Test::checkResult(Ljava/lang/Object;)(this.@client.Test::applet.readFloat());
  }-*/;

  private int readInt() throws IOException {
    try {
      return readIntImpl();
    }
    catch (JavaScriptException e) {
      throw getException(e);
    }
  }

  private native int readIntImpl()/*-{
    return @client.Test::checkResult(Ljava/lang/Object;)(this.@client.Test::applet.readInt());
  }-*/;

  private short readShort() throws IOException {
    try {
      return readShortImpl();
    }
    catch (JavaScriptException e) {
      throw getException(e);
    }
  }

  private native short readShortImpl()/*-{
    return @client.Test::checkResult(Ljava/lang/Object;)(this.@client.Test::applet.readShort());
  }-*/;

  private int readUnsignedByte() throws IOException {
    try {
      return readUnsignedByteImpl();
    }
    catch (JavaScriptException e) {
      throw getException(e);
    }
  }

  private native int readUnsignedByteImpl()/*-{
    return @client.Test::checkResult(Ljava/lang/Object;)(this.@client.Test::applet.readUnsignedByte());
  }-*/;

  private long readUnsignedInt() throws IOException {
    try {
      return readUnsignedIntImpl();
    }
    catch (JavaScriptException e) {
      throw getException(e);
    }
  }

  private native long readUnsignedIntImpl()/*-{
    return @client.Test::checkResult(Ljava/lang/Object;)(this.@client.Test::applet.readUnsignedInt());
  }-*/;

  private int readUnsignedShort() throws IOException {
    try {
      return readUnsignedShortImpl();
    }
    catch (JavaScriptException e) {
      throw getException(e);
    }
  }

  private native int readUnsignedShortImpl()/*-{
    return @client.Test::checkResult(Ljava/lang/Object;)(this.@client.Test::applet.readUnsignedShort());
  }-*/;

  private String readUTF() throws IOException {
    try {
      return readUTFImpl();
    }
    catch (JavaScriptException e) {
      throw getException(e);
    }
  }

  private native String readUTFImpl()/*-{
    return @client.Test::checkResult(Ljava/lang/Object;)(this.@client.Test::applet.readUTF());
  }-*/;

  private String readString(int length, String charSet) throws IOException {
    try {
      return readStringImpl(length, charSet);
    }
    catch (JavaScriptException e) {
      throw getException(e);
    }
  }

  private native String readStringImpl(int length, String charSet)/*-{
    return @client.Test::checkResult(Ljava/lang/Object;)(this.@client.Test::applet.readString(length, charSet));
  }-*/;

  private void writeBoolean(boolean value) throws IOException {
    try {
      writeBooleanImpl(value);
    }
    catch (JavaScriptException e) {
      throw getException(e);
    }
  }

  private native void writeBooleanImpl(boolean value)/*-{
    @client.Test::checkResult(Ljava/lang/Object;)(this.@client.Test::applet.writeBoolean(value));
  }-*/;

  private void writeByte(int value) throws IOException {
    try {
      writeByteImpl(value);
    }
    catch (JavaScriptException e) {
      throw getException(e);
    }
  }

  private native void writeByteImpl(int value)/*-{
    @client.Test::checkResult(Ljava/lang/Object;)(this.@client.Test::applet.writeByte(value));
  }-*/;

  private void writeBytes(byte[] bytes) throws IOException {
    writeBytes(bytes, 0, bytes.length);
  }

  private void writeBytes(byte[] bytes, int offset, int length) throws IOException {
    if (bytes == null)
      throw new NullPointerException();

    if (offset < 0 || length < 0 || offset + length > bytes.length)
      throw new IndexOutOfBoundsException();

    if (length == 0)
      return;

    // We couldn't pass arrays from JavaScript to Java Applet, so we need to convert byte array to a string
    String bytesStr = String.valueOf((char) bytes[offset]);
    for (int i = 1; i < length; i++)
      bytesStr += String.valueOf((char) bytes[offset + i]);

    try {
      writeBytesImpl(bytesStr);
    }
    catch (JavaScriptException e) {
      throw getException(e);
    }
  }

  private native void writeBytesImpl(String bytesStr)/*-{
    @client.Test::checkResult(Ljava/lang/Object;)(this.@client.Test::applet.writeBytes(bytesStr));
  }-*/;

  private void writeDouble(double value) throws IOException {
    try {
      writeDoubleImpl(value);
    }
    catch (JavaScriptException e) {
      throw getException(e);
    }
  }

  private native void writeDoubleImpl(double value)/*-{
    @client.Test::checkResult(Ljava/lang/Object;)(this.@client.Test::applet.writeDouble(value));
  }-*/;

  private void writeFloat(float value) throws IOException {
    try {
      writeFloatImpl(value);
    }
    catch (JavaScriptException e) {
      throw getException(e);
    }
  }

  private native void writeFloatImpl(float value)/*-{
    @client.Test::checkResult(Ljava/lang/Object;)(this.@client.Test::applet.writeFloat(value));
  }-*/;

  private void writeInt(int value) throws IOException {
    try {
      writeIntImpl(value);
    }
    catch (JavaScriptException e) {
      throw getException(e);
    }
  }

  private native void writeIntImpl(int value)/*-{
    @client.Test::checkResult(Ljava/lang/Object;)(this.@client.Test::applet.writeInt(value));
  }-*/;

  private void writeShort(short value) throws IOException {
    try {
      writeShortImpl(value);
    }
    catch (JavaScriptException e) {
      throw getException(e);
    }
  }

  private native void writeShortImpl(short value)/*-{
    @client.Test::checkResult(Ljava/lang/Object;)(this.@client.Test::applet.writeShort(value));
  }-*/;

  private void writeUnsignedInt(long value) throws IOException {
    try {
      writeUnsignedIntImpl(value);
    }
    catch (JavaScriptException e) {
      throw getException(e);
    }
  }

  private native void writeUnsignedIntImpl(long value)/*-{
    @client.Test::checkResult(Ljava/lang/Object;)(this.@client.Test::applet.writeUnsignedInt(value));
  }-*/;

  private void writeUTF(String value) throws IOException {
    try {
      writeUTFImpl(value);
    }
    catch (JavaScriptException e) {
      throw getException(e);
    }
  }

  private native void writeUTFImpl(String value)/*-{
    @client.Test::checkResult(Ljava/lang/Object;)(this.@client.Test::applet.writeUTF(value));
  }-*/;

  private void writeString(String value, String charSet) throws IOException {
    try {
      writeStringImpl(value, charSet);
    }
    catch (JavaScriptException e) {
      throw getException(e);
    }
  }

  private native void writeStringImpl(String value, String charSet)/*-{
    @client.Test::checkResult(Ljava/lang/Object;)(this.@client.Test::applet.writeString(value, charSet));
  }-*/;

}
